#include "config.h"

#include <stdlib.h>

#define DBG_SUBSYS      S_LIBINTERFACE

#include "iscsi.h"
#include "iscsi_mem_cache.h"
#include "dbg.h"

#define MEM_CACHE_POOL_ID_UNUSED        -1

/*
 * Attach a `mem_mcache_info' to tail of each structure, used for quick
 * alloc and free.
 */
struct mem_mcache_info {
        s64 pool_id;
};

#define mem_mcache_info(cachep, obj) \
        ((struct mem_mcache_info *)((obj) + (cachep)->unit_size))

/*
 * iscsi_mem_mcache_create -
 *
 * @name: the name of this memory pool
 * @size: size of unit
 * @base_nr: basic number of unit
 * @align: is the start of memory alloced need to align with page size
 *
 * @return the address of memory on success, otherwise NULL is returned.
 */
struct iscsi_mem_cache *iscsi_mem_mcache_create(char *name, u32 size, u32 base_nr, u8 align)
{
        int ret, i = 0;
        void *obj;
        struct iscsi_mem_cache *cachep;
        struct mem_mcache_info *info;

        if (!size || !base_nr) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        // TODO valgrind: memory leak
        /*
         * ==21518== 286,167 (40 direct, 286,127 indirect) bytes in 1 blocks are definitely lost in loss record 647 of 701
         * ==21518==    at 0x4C29975: calloc (vg_replace_malloc.c:711)
         * ==21518==    by 0x56216A9: iscsi_mem_mcache_create (iscsi_mem_cache.c:46)
         * ==21518==    by 0x563A056: mem_mcache_init (conn.c:34)
         * ==21518==    by 0x563AA82: conn_alloc (conn.c:160)
         * ==21518==    by 0x561EA40: __iscsi_accept__ (iscsid.c:985)
         * ==21518==    by 0x561F4F1: __iscsi_accept (iscsid.c:1026)
         * ==21518==    by 0x59FCDC4: start_thread (in /usr/lib64/libpthread-2.17.so)
         * ==21518==    by 0x77DD73C: clone (in /usr/lib64/libc-2.17.so)
         */
        cachep = calloc(1, sizeof(*cachep));
        if (!cachep) {
                ret = ENOMEM;
                GOTO(err_ret, ret);
        }

        cachep->name = strdup(name);
        cachep->base_nr = base_nr;
        cachep->max_nr = base_nr;
        cachep->idx = 0;
        cachep->align = align;
        cachep->unit_size = size;
        cachep->real_size = size + sizeof(struct mem_mcache_info);

        cachep->pool = calloc(cachep->max_nr, sizeof(void *));
        if (!cachep->pool) {
                ret = ENOMEM;
                GOTO(cachep_free, ret);
        }

        for (i = 0; i < (int)cachep->max_nr; ++i) {
                /*
                 * NOTE ALIGN: if the @align is not zero, we need the alloced memory
                 * address be nultiple of the page size, this is usefull when use the
                 * O_DIRECT flag to open a file.
                 */
                obj = cachep->align ? valloc(cachep->real_size) : malloc(cachep->real_size);
                if (!obj) {
                        ret = ENOMEM;
                        GOTO(pool_free, ret);
                }
                cachep->pool[i] = obj;

                info = mem_mcache_info(cachep, obj);
                info->pool_id = MEM_CACHE_POOL_ID_UNUSED;
        }

        return cachep;
pool_free:
        for (--i; i >= 0; --i) {
                free(cachep->pool[i]);
        }
        free(cachep->pool);
cachep_free:
        free(cachep->name);
        free(cachep);
err_ret:
        return NULL;
}

/*
 * NOTE: Caller must hold the lock of cache.
 */
static int __iscsi_mem_mcache_expand(struct iscsi_mem_cache *cachep, u8 flag)
{
        int ret;
        u32 new_nr, old_nr, i = 0;
        void **new, *obj;
        struct mem_mcache_info *info;

        old_nr = cachep->max_nr;
        new_nr = old_nr * 2;

        DBUG("expand mem cache %s(%p) from %u to %u\n",
             cachep->name, cachep, old_nr, new_nr);

        while (!(new = calloc(new_nr, sizeof(void *)))) {
                if (flag & MC_FLAG_NOFAIL) {
                        sleep(1);
                        DWARN("no memory, wait success !!!\n");
                        continue;
                }
                ret = ENOMEM;
                GOTO(err_ret, ret);
        }

        for (i = old_nr; i < new_nr; ++i) {
                while (1) {
                        /*
                         * NOTE ALIGN: if the @align is not zero, we need the alloced memory
                         * address be nultiple of the page size, this is usefull when use the
                         * O_DIRECT flag to open a file.
                         */
                        obj = cachep->align ? valloc(cachep->real_size) : malloc(cachep->real_size);
                        if (!obj) {
                                if (flag & MC_FLAG_NOFAIL) {
                                        sleep(1);
                                        DWARN("no memory, wait success !!!\n");
                                        continue;
                                }
                                ret = ENOMEM;
                                GOTO(pool_free, ret);
                        }

                        break;
                }

                new[i] = obj;

                info = mem_mcache_info(cachep, obj);
                info->pool_id = MEM_CACHE_POOL_ID_UNUSED;
        }

        memcpy(new, cachep->pool, old_nr * sizeof(void *));
        free(cachep->pool);

        cachep->pool = new;
        cachep->max_nr = new_nr;

        return 0;
pool_free:
        for (--i; i >= old_nr;  --i) {
                free(new[i]);
        }
        free(new);
err_ret:
        return ret;
}

/*
 * NOTE: Caller must hold the lock of cache.
 */
static int __iscsi_mem_mcache_shrink(struct iscsi_mem_cache *cachep)
{
        int ret;
        u32 new_nr, old_nr, i = 0;
        void **new;

        old_nr = cachep->max_nr;
        new_nr = old_nr / 2;

        DBUG("shrink mem cache %s(%p) from %u to %u\n",
             cachep->name, cachep, old_nr, new_nr);

        new = calloc(new_nr, sizeof(void *));
        if (!new) {
                ret = ENOMEM;
                GOTO(err_ret, ret);
        }

        for (i = new_nr; i < old_nr; ++i)
                free(cachep->pool[i]);

        memcpy(new, cachep->pool, new_nr * sizeof(void *));
        free(cachep->pool);

        cachep->pool = new;
        cachep->max_nr = new_nr;

        return 0;
err_ret:
        return ret;
}

void *iscsi_mem_mcache_alloc(struct iscsi_mem_cache *cachep, u8 flag)
{
        int ret;
        void *obj;
        struct mem_mcache_info *info;

        if (cachep->idx == cachep->max_nr) {
                ret = __iscsi_mem_mcache_expand(cachep, flag);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        obj = cachep->pool[cachep->idx];
        info = mem_mcache_info(cachep, obj);
        info->pool_id = cachep->idx;
        ++cachep->idx;

        return obj;
err_ret:
        return NULL;
}

void *iscsi_mem_mcache_calloc(struct iscsi_mem_cache *cachep, u8 flag)
{
        void *obj = iscsi_mem_mcache_alloc(cachep, flag);
        if (obj)
                memset(obj, 0x00, cachep->unit_size);
        return obj;
}

void iscsi_mem_mcache_free(struct iscsi_mem_cache *cachep, void *del_obj)
{
        u32 del_idx, last_idx;
        void *last_obj;
        struct mem_mcache_info *del_info, *last_info;

        del_info = mem_mcache_info(cachep, del_obj);
        del_idx = del_info->pool_id;

        if (del_idx != cachep->idx - 1) {
                last_obj = cachep->pool[cachep->idx - 1];
                last_info = mem_mcache_info(cachep, last_obj);
                last_idx = last_info->pool_id;

                cachep->pool[del_idx] = last_obj;
                last_info->pool_id = del_idx;
                cachep->pool[last_idx] = del_obj;
        }
        del_info->pool_id = MEM_CACHE_POOL_ID_UNUSED;

        --cachep->idx;

        if (cachep->max_nr > cachep->base_nr &&
            cachep->idx < cachep->max_nr / 8)
                (void) __iscsi_mem_mcache_shrink(cachep);
}

void iscsi_mem_mcache_destroy(struct iscsi_mem_cache *cachep)
{
        u32 i;

        for (i = 0; i < cachep->max_nr; ++i)
                free(cachep->pool[i]);

        free(cachep->name);
        free(cachep->pool);
        free(cachep);
}
